<!DOCTYPE html>
<html lang="zh-CN" :data-theme="currentTheme">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>deepseek 调试</title>
  <script src="vue.global.min-3.5.13.js"></script>
  <script src="marked.min-15.0.6.js"></script>
  <!-- Tailwind CSS -->
  <link href="full.min-4.12.23.css" rel="stylesheet" type="text/css" />
  <script src="tailwindcss-3.4.16.js"></script>
  <script>
    tailwind.config = {
      daisyui: {
        themes: [{
          light: {
            "primary": "#570DF8",
            "primary-focus": "#4506CB",
            "secondary": "#F000B8",
            "accent": "#37CDBE",
            "neutral": "#3D4451",
            "base-100": "#FFFFFF",
            "base-200": "#F2F2F2",
            "base-300": "#E5E6E6",
            "base-content": "#1F2937",
            "info": "#3ABFF8",
            "success": "#36D399",
            "warning": "#FBBD23",
            "error": "#F87272"
          },
          dark: {
            "primary": "#BB86FC",
            "primary-focus": "#9965E3",
            "secondary": "#03DAC6",
            "accent": "#BB86FC",
            "neutral": "#121212",
            "base-100": "#1E1E1E",
            "base-200": "#2C2C2C",
            "base-300": "#242424",
            "base-content": "#E1E1E1",
            "info": "#0175C2",
            "success": "#00C853",
            "warning": "#FFB74D",
            "error": "#CF6679",
          }
        }],
      },
    }
  </script>
  <style>
    /* 暗色模式下的滚动条样式 */
    [data-theme='dark'] ::-webkit-scrollbar {
      width: 8px;
      height: 8px;
    }

    [data-theme='dark'] ::-webkit-scrollbar-track {
      background: #2C2C2C;
      border-radius: 4px;
    }

    [data-theme='dark'] ::-webkit-scrollbar-thumb {
      background: #424242;
      border-radius: 4px;
    }

    [data-theme='dark'] ::-webkit-scrollbar-thumb:hover {
      background: #505050;
    }
  </style>
</head>
<body class="h-screen transition-colors duration-200 flex flex-col overflow-hidden">
<div id="app">
  <!-- 导航栏 -->
  <div class="navbar bg-base-100 shadow-lg px-4 flex-none h-16">
    <div class="flex-1">
      <h1 class="text-xl font-bold">Deepseek 调试工具</h1>
    </div>
    <div class="flex-none gap-2">
      <div class="dropdown dropdown-end">
        <label class="swap swap-rotate btn btn-ghost btn-circle">
          <input type="checkbox"
                 :checked="currentTheme === 'dark'"
                 @change="toggleTheme"
                 class="theme-controller"/>
          <!-- sun icon -->
          <svg class="swap-on fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/></svg>
          <!-- moon icon -->
          <svg class="swap-off fill-current w-5 h-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/></svg>
        </label>
      </div>
    </div>
  </div>

  <main class="h-[calc(100vh-8rem)] container mx-auto px-4 py-2 overflow-hidden">
    <div class="flex gap-4 h-full">
      <!-- 左侧原始数据区域 -->
      <div class="w-1/2 flex flex-col">
        <!-- 头部控制区 -->
        <div class="bg-base-100 dark:bg-base-200 rounded-box shadow-lg dark:shadow-lg/20 p-4 mb-2 flex-none backdrop-blur-sm">
          <div class="mb-4 flex items-center">
            <label class="label w-20">
              <span class="label-text dark:text-base-content/80">SSE地址</span>
            </label>
            <input
              type="text"
              v-model="sseUrl"
              placeholder="请输入SSE地址"
              class="input input-bordered w-full dark:bg-base-300 dark:border-base-content/10 dark:text-base-content/90 dark:placeholder-base-content/50"
            >
          </div>
          <div class="flex space-x-2">
            <button
              @click="connect"
              :disabled="isConnected"
              class="btn btn-primary"
            >
              连接
            </button>
            <button
              @click="disconnect"
              :disabled="!isConnected"
              class="btn btn-error"
            >
              断开
            </button>
            <button
              @click="clearMessages"
              class="btn btn-ghost"
            >
              清空
            </button>
          </div>
        </div>

        <!-- 消息展示区 -->
        <div class="flex-1 flex flex-col min-h-0">
          <h2 class="text-xl font-bold mb-1 dark:text-base-content/90">原始数据</h2>
          <div class="flex-1 overflow-y-auto min-h-0 messages-container">
            <div class="mockup-window bg-base-300 dark:bg-base-300/50 h-full flex flex-col backdrop-blur-sm">
              <div class="flex-1 px-6 py-4 bg-base-200 dark:bg-base-200/50 overflow-y-auto">
                <div v-if="messages.length === 0" class="text-gray-400 dark:text-gray-500 text-center py-4">
                  暂无消息
                </div>
                <div
                  v-for="(message, index) in messages"
                  :key="index"
                  class="border-b border-base-200 last:border-0 py-3"
                >
                  <div class="text-xs opacity-70 mb-1">{{ message.time }}</div>
                  <pre class="mockup-code mt-2">{{ message.data }}</pre>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- 右侧推理过程和答案区域 -->
      <div class="w-1/2 flex flex-col gap-2 min-h-0">
        <div class="flex-1 flex flex-col min-h-0">
          <h2 class="text-xl font-bold mb-1">推理过程</h2>
          <div ref="reasoningRef" class="flex-1 overflow-y-auto min-h-0">
            <div class="mockup-window bg-base-300 h-full flex flex-col">
              <div class="flex-1 px-6 py-4 bg-base-200 overflow-y-auto">
                {{ reasoningChain }}
              </div>
            </div>
          </div>
        </div>

        <div class="flex-1 flex flex-col min-h-0">
          <h2 class="text-xl font-bold mb-1">最终答案</h2>
          <div ref="answerRef" class="flex-1 overflow-y-auto min-h-0">
            <div class="mockup-window bg-base-300 h-full flex flex-col">
              <div class="flex-1 px-6 py-4 bg-base-200 overflow-y-auto prose dark:prose-invert max-w-none" v-html="finalAnswer">
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </main>

  <!-- Footer -->
  <footer class="footer footer-center p-2 bg-base-300 dark:bg-base-300/50 text-base-content dark:text-base-content/70 flex-none h-12 backdrop-blur-sm">
    <aside class="flex items-center gap-2">
      <p>Powered by</p>
      <div class="flex items-center gap-1">
        <span class="font-semibold">Pig AI</span>
      </div>
    </aside>
  </footer>
</div>

<script>
  const { createApp, ref, computed, watch, nextTick, onMounted } = Vue

  createApp({
    setup() {
      const sseUrl = ref('http://127.0.0.1:8080/chat?prompt=新能源汽车优势在哪里？')
      const messages = ref([])
      const isConnected = ref(false)
      const currentTheme = ref('light')
      const isInThinkTag = ref(false)
      let eventSource = null
      const reasoningRef = ref(null)
      const answerRef = ref(null)

      // 计算推理过程（思考链）
      const reasoningChain = computed(() => {
        return messages.value
          .filter(m => m.parsed?.reasoning_content)
          .map(m => m.parsed.reasoning_content)
          .join('')
      })

      // 计算最终答案
      const finalAnswer = computed(() => {
        const rawContent = messages.value
          .filter(m => m.parsed?.content)
          .map(m => m.parsed.content)
          .join('')
        return marked.parse(rawContent)
      })

      // 初始化主题
      onMounted(() => {
        const savedTheme = localStorage.getItem('theme')
        currentTheme.value = savedTheme || 'light'
        document.documentElement.setAttribute('data-theme', currentTheme.value)
      })

      // 切换主题
      const toggleTheme = () => {
        currentTheme.value = currentTheme.value === 'light' ? 'dark' : 'light'
        localStorage.setItem('theme', currentTheme.value)
        document.documentElement.setAttribute('data-theme', currentTheme.value)
      }

      // 滚动到底部的函数
      const scrollToBottom = (element) => {
        if (element) {
          // 获取实际的滚动容器（mockup-window 内的 overflow-y-auto 元素）
          const scrollContainer = element.querySelector('.overflow-y-auto')
          if (scrollContainer) {
            scrollContainer.scrollTop = scrollContainer.scrollHeight
          }
        }
      }

      // 监听消息变化，自动滚动
      watch(() => [messages.value.length, reasoningChain.value, finalAnswer.value], () => {
        nextTick(() => {
          if (reasoningRef.value) {
            scrollToBottom(reasoningRef.value)
          }
          if (answerRef.value) {
            scrollToBottom(answerRef.value)
          }
          // 滚动原始数据区域
          const messagesContainer = document.querySelector('.messages-container .overflow-y-auto')
          if (messagesContainer) {
            messagesContainer.scrollTop = messagesContainer.scrollHeight
          }
        })
      }, { deep: true })

      const parseSSEData = (data) => {
        try {
          const parsed = JSON.parse(data)

          // 检查是否直接返回了 reasoning_content
          const directReasoning = parsed.choices?.[0]?.delta?.reasoning_content
          if (directReasoning) {
            return {
              id: parsed.id,
              created: parsed.created,
              model: parsed.model,
              reasoning_content: directReasoning,
              content: parsed.choices?.[0]?.delta?.content || ''
            }
          }

          const content = parsed.choices?.[0]?.delta?.content || ''

          // 处理 think 标签包裹的情况
          if (content.includes('<think>')) {
            isInThinkTag.value = true
            const startIndex = content.indexOf('<think>') + '<think>'.length
            return {
              id: parsed.id,
              created: parsed.created,
              model: parsed.model,
              reasoning_content: content.substring(startIndex),
              content: content.substring(0, content.indexOf('<think>'))
            }
          }

          if (content.includes('</think>')) {
            isInThinkTag.value = false
            const endIndex = content.indexOf('</think>')
            return {
              id: parsed.id,
              created: parsed.created,
              model: parsed.model,
              reasoning_content: content.substring(0, endIndex),
              content: content.substring(endIndex + '</think>'.length)
            }
          }

          // 根据状态决定内容归属
          return {
            id: parsed.id,
            created: parsed.created,
            model: parsed.model,
            reasoning_content: isInThinkTag.value ? content : '',
            content: isInThinkTag.value ? '' : content
          }
        } catch (e) {
          console.error('解析JSON失败:', e)
          return null
        }
      }

      const connect = () => {
        if (eventSource) {
          eventSource.close()
        }

        // 清空消息
        clearMessages()

        try {
          eventSource = new EventSource(sseUrl.value)
          isConnected.value = true

          eventSource.onmessage = (event) => {
            const parsed = parseSSEData(event.data)
            messages.value.push({
              time: new Date().toLocaleTimeString(),
              data: event.data,
              parsed: parsed
            })
          }

          eventSource.onerror = (error) => {
            console.error('SSE Error:', error)
            disconnect()
          }
        } catch (error) {
          console.error('Connection Error:', error)
          disconnect()
        }
      }

      const disconnect = () => {
        if (eventSource) {
          eventSource.close()
          eventSource = null
        }
        isConnected.value = false
      }

      const clearMessages = () => {
        messages.value = []
      }

      return {
        sseUrl,
        messages,
        isConnected,
        currentTheme,
        toggleTheme,
        connect,
        disconnect,
        clearMessages,
        reasoningChain,
        finalAnswer,
        reasoningRef,
        answerRef
      }
    }
  }).mount('#app')
</script>
</body>
</html>
